package smart.rpc.client;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang.StringUtils;
import smart.rpc.common.SmartRPCRequest;
import smart.rpc.exception.SmartRPCException;

import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;

/**
 * Created By gao_e on 2020/3/8 10:24
 */
public class ClientServiceInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("This is proxy invoke method before.");
        // 代理逻辑执行前，需要先知道服务信息地址(从注册中心拉取服务信息)
        getServiceInfoFromFileAdd2ClientContainer();
        // 获取接口全名
        String interfaceName = method.getDeclaringClass().getName();
        // 方法名称
        String methodName = method.getName();
        // 获取方法参数类型集合
        Class<?>[] parameterTypes = method.getParameterTypes();
        String host = null;
        int port = 0;
        String serviceName = null;
        try {
            // [ip,端口,serviceFullName]
            String[] serverInfos = ClientContainer.map.get(interfaceName);
            host = serverInfos[0];
            port = Integer.valueOf(serverInfos[1]);
            serviceName = serverInfos[2];
        } catch (Exception e) {
            throw new SmartRPCException("SERVER INFO GET FAILD , INTERFACENAME = " + interfaceName);
        }
        if (StringUtils.isEmpty(host) || port == 0 || StringUtils.isEmpty(serviceName))
            throw new SmartRPCException("SERVER INFO ERROR , INTERFACENAME = " + interfaceName);
        // 构建请求对象
        SmartRPCRequest smartRPCRequest = new SmartRPCRequest(UUID.randomUUID().toString(),
                interfaceName, methodName, parameterTypes, args, serviceName, serviceName);
        smartRPCRequest.setResultCls(method.getReturnType());
        // 执行具体代理逻辑，RPC远程请求服务端，获取响应结果
        Object result = rpcProxyInvoke(host, port, smartRPCRequest);
        System.out.println("This is proxy invoke method after. result = " + result);
        return result;
    }

    private Object rpcProxyInvoke(String host, int port, SmartRPCRequest request) {
        // 判断这个服务端连接是否已经存在
        StartNettyClientConnectTask startNettyClientConnectTask = ClientContainer.serverMap.get(host + ":" + port);
        if (startNettyClientConnectTask == null) {
            startNettyClientConnectTask = new StartNettyClientConnectTask(host, port);
            // 将任务添加到一个独立的线程中异步执行
            startNettyClientConnectTask.addClientConnectRunnableTask2InnerThreadPoolAndExecuteSelf();
            // 当前客户端服务针对同一个服务的调用，不会开辟额外的线程做任务处理
            ClientContainer.serverMap.put(host + ":" + port, startNettyClientConnectTask);
        }
        // 将请求任务添加到队列中
        startNettyClientConnectTask.addedRequest(request);
        // 同步等待响应结果——远程处理结果
        Object result = startNettyClientConnectTask.synchGetResult(request.getRequestId());
        System.out.println("This is proxy invoke method after, result is : " + result);
        return result;
    }


    //　从注册中心拉取服务数据
    private void getServiceInfoFromFileAdd2ClientContainer() {
        // 已经拉取过注册信息则不必再次拉取
        if (ClientContainer.map.size() > 0)
            return;
        // 获取服务信息到本地
        if (StringUtils.isEmpty(ClientContainer.registHost))
            throw new SmartRPCException("SERVER INFO REGISTHOST IS EMPTY ERROR");
        File file = new File(ClientContainer.registHost);
        BufferedReader br = null;
        InputStreamReader isr = null;
        try {
            isr = new InputStreamReader(new FileInputStream(file), "utf-8");
            br = new BufferedReader(isr);
            String s;
            while ((s = br.readLine()) != null) {
                // 文件内容示例：
                // {"smart.rpc.producer.contract.facade.ISmartRPCTestService":["192.168.0.105","10000","smartRPCTestService"]}
                Map<String, String[]> map = JSONObject.parseObject(s, new TypeReference<Map<String, String[]>>() {});
                ClientContainer.map.putAll(map);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
